1   // Copyright 2007-2013 The Apache Software Foundation
2   //
3   // Licensed under the Apache License, Version 2.0 (the "License");
4   // you may not use this file except in compliance with the License.
5   // You may obtain a copy of the License at
6   //
7   // http://www.apache.org/licenses/LICENSE-2.0
8   //
9   // Unless required by applicable law or agreed to in writing, software
10  // distributed under the License is distributed on an "AS IS" BASIS,
11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  // See the License for the specific language governing permissions and
13  // limitations under the License.
14  
15  package org.apache.tapestry5.internal.services;
16  
17  import java.io.IOException;
18  
19  import org.apache.tapestry5.TrackableComponentEventCallback;
20  import org.apache.tapestry5.internal.InternalConstants;
21  import org.apache.tapestry5.internal.structure.ComponentPageElement;
22  import org.apache.tapestry5.internal.structure.Page;
23  import org.apache.tapestry5.internal.util.Holder;
24  import org.apache.tapestry5.ioc.internal.util.TapestryException;
25  import org.apache.tapestry5.json.JSONObject;
26  import org.apache.tapestry5.services.Ajax;
27  import org.apache.tapestry5.services.ComponentEventRequestHandler;
28  import org.apache.tapestry5.services.ComponentEventRequestParameters;
29  import org.apache.tapestry5.services.ComponentEventResultProcessor;
30  import org.apache.tapestry5.services.Environment;
31  import org.apache.tapestry5.services.Request;
32  
33  /**
34   * Similar to {@link ComponentEventRequestHandlerImpl}, but built around the Ajax request cycle, where the action
35   * request sends back an immediate JSON response containing the new content.
36   */
37  @SuppressWarnings({"unchecked", "rawtypes"})
38  public class AjaxComponentEventRequestHandler implements ComponentEventRequestHandler
39  {
40      private final RequestPageCache cache;
41  
42      private final Request request;
43  
44      private final PageRenderQueue queue;
45  
46      private final ComponentEventResultProcessor resultProcessor;
47  
48      private final Environment environment;
49  
50      private final AjaxPartialResponseRenderer partialRenderer;
51  
52      private final PageActivator pageActivator;
53  
54      public AjaxComponentEventRequestHandler(RequestPageCache cache, Request request, PageRenderQueue queue, @Ajax
55      ComponentEventResultProcessor resultProcessor, PageActivator pageActivator,
56                                              Environment environment,
57                                              AjaxPartialResponseRenderer partialRenderer)
58      {
59          this.cache = cache;
60          this.queue = queue;
61          this.resultProcessor = resultProcessor;
62          this.pageActivator = pageActivator;
63          this.request = request;
64          this.environment = environment;
65          this.partialRenderer = partialRenderer;
66      }
67  
68      public void handle(ComponentEventRequestParameters parameters) throws IOException
69      {
70          Page activePage = cache.get(parameters.getActivePageName());
71  
72          final Holder<Boolean> resultProcessorInvoked = Holder.create();
73          resultProcessorInvoked.put(false);
74  
75          ComponentEventResultProcessor interceptor = new ComponentEventResultProcessor()
76          {
77              public void processResultValue(Object value) throws IOException
78              {
79                  resultProcessorInvoked.put(true);
80  
81                  resultProcessor.processResultValue(value);
82              }
83          };
84  
85          // If we end up doing a partial render, the page render queue service needs to know the
86          // page that will be rendered (for logging purposes, if nothing else).
87  
88          queue.setRenderingPage(activePage);
89  
90          request.setAttribute(InternalConstants.PAGE_NAME_ATTRIBUTE_NAME, parameters.getActivePageName());
91  
92          if (pageActivator.activatePage(activePage.getRootElement().getComponentResources(), parameters
93                  .getPageActivationContext(), interceptor))
94              return;
95  
96          Page containerPage = cache.get(parameters.getContainingPageName());
97  
98          ComponentPageElement element = containerPage.getComponentElementByNestedId(parameters.getNestedComponentId());
99  
100         // In many cases, the triggered element is a Form that needs to be able to
101         // pass its event handler return values to the correct result processor.
102         // This is certainly the case for forms.
103 
104         TrackableComponentEventCallback callback = new ComponentResultProcessorWrapper(interceptor);
105 
106         environment.push(ComponentEventResultProcessor.class, interceptor);
107         environment.push(TrackableComponentEventCallback.class, callback);
108 
109         boolean handled = element
110                 .triggerContextEvent(parameters.getEventType(), parameters.getEventContext(), callback);
111 
112         if (!handled)
113             throw new TapestryException(String.format("Request event '%s' (on component %s) was not handled; you must provide a matching event handler method in the component or in one of its containers.", parameters.getEventType(), element.getCompleteId()), element,
114                     null);
115 
116         environment.pop(TrackableComponentEventCallback.class);
117         environment.pop(ComponentEventResultProcessor.class);
118 
119 
120         // If the result processor was passed a value, then it will already have rendered. Otherwise it was not passed a value,
121         // but it's still possible that we still want to do a partial page render ... if filters were added to the render queue.
122         // In that event, run the partial page render now and return.
123 
124         boolean wasInvoked = resultProcessorInvoked.get();
125 
126         if ((!wasInvoked) && queue.isPartialRenderInitialized())
127         {
128             partialRenderer.renderPartialPageMarkup();
129             return;
130         }
131 
132         // If the result processor was passed a value, then it will already have rendered, and there is nothing more to do.
133 
134         if (wasInvoked) { return; }
135 
136         // Send an empty JSON reply if no value was returned from the component event handler method.
137         // This is the typical behavior when an Ajax component event handler returns null. It still
138         // will go through a pipeline that will add information related to partial page rendering.
139 
140         resultProcessor.processResultValue(new JSONObject());
141     }
142 }